
/**
 * AppleIIGo
 * FDI Disk Image class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "FDIDiskImage.h"

bool FDIDiskImage::isDiskImageValid(UINT32 diskType, char * diskImagePath) {
	FILE * diskImageFile;
	if (!(diskImageFile = fopen(diskImagePath, "rb")))
		return false;
	
	bool isError = !readFdiHeader(diskType, diskImageFile, 0, 0, 0);
	
	fclose(diskImageFile);
	
	return !isError;
}

FDIDiskImage::FDIDiskImage(UINT32 diskType, char * diskImagePath) :
FloppyDiskImage(diskType, diskImagePath) {
	if (!isDiskImageOpened())
		return;
	
	bool isWriteProtectionForced;
	if (!readDiskImage(diskType, &isWriteProtectionForced, true))
		return;
	
	if (!allocateDisk())
		return;
	
	if (!readDiskImage(diskType, &isWriteProtectionForced, false))
		return;
	
	setWriteProtected(isWriteProtected() || isWriteProtectionForced);
}

FDIDiskImage::~FDIDiskImage() {
	if (!isDiskImageOpened())
		return;
	
	if (!isModified())
		return;
	
	fclose(diskImageFile);
	diskImageFile = fopen(getDiskImagePath(), "wb");
	if (diskImageFile)
		writeDiskImage(diskImageFile, this);
}

bool FDIDiskImage::readFdiHeader(UINT32 diskType,
								 FILE * diskImageFile,
								 UINT32 * diskImageTrackNum,
								 FdiHeaderTrackType * fdiTracks,
								 bool * isWriteProtectionForced) {
	FdiHeaderType fdiHeader;
	
	fseek(diskImageFile, 0, SEEK_END);
	UINT32 fdiByteNum = ftell(diskImageFile);
	fseek(diskImageFile, 0, SEEK_SET);
	
	if (fread(&fdiHeader, FDIHEADER_BLOCKBYTENUM, 1, diskImageFile) != 1)
		return false;
	
	if (strncmp(fdiHeader.signature, FDISIGNATURE, strlen(FDISIGNATURE)))
		return false;
	
	if (getBigEndianUINT16(&fdiHeader.version) != FDIVERSION)
		return false;
	
	UINT32 trackNum = getBigEndianUINT16(&fdiHeader.ltrack) + 1;
	UINT32 headNum = fdiHeader.lhead + 1;
	UINT32 fdiTrackNum = trackNum * headNum;
	UINT32 fdiType = fdiHeader.type;
	UINT32 fdiFlags = fdiHeader.flags;
	bool isWriteProtected = getBit(fdiFlags, FDIFLAGS_WRITEPROTECTED);
	UINT32 fdiTpi = fdiHeader.tpi;
	UINT32 fdiHeadWidth = fdiHeader.headwidth;
	
	switch (diskType) {
		case FLOPPYDISKTYPE_5_25:
			if (fdiType != FDIDISKTYPE_5_25)
				return false;
			if ((trackNum > DISK_5_25_TRACKNUM) ||
				(headNum != DISK_5_25_HEADNUM))
				return false;
			if ((fdiTpi != FDITPI_192) || (fdiHeadWidth != FDITPI_192))
				return true;
			break;
		case FLOPPYDISKTYPE_3_5:
			if (fdiType != FDIDISKTYPE_3_5)
				return false;
			if ((trackNum > DISK_3_5_TRACKNUM) ||
				(headNum != DISK_3_5_HEADNUM))
				return false;
			if ((fdiTpi != FDITPI_96) || (fdiHeadWidth != FDITPI_96))
				return true;
			break;
		default:
			return false;
	}
	
	UINT32 byteNum = FDIHEADER_BLOCKBYTENUM;
	if (fdiTrackNum > FDIHEADER_FIRSTBLOCKTRACKNUM) {
		byteNum += FDIHEADER_BLOCKBYTENUM;
		if (fread((UINT8 *) (&fdiHeader) + FDIHEADER_BLOCKBYTENUM,
				  FDIHEADER_BLOCKBYTENUM, 1, diskImageFile) != 1)
			return false;
	}
	
	for (UINT32 index = 0; index < fdiTrackNum; index++) {
		UINT32 fdiTrackType = fdiHeader.tracks[index].type;
		switch (fdiTrackType) {
			case FDITRACKTYPE_BLANK:
			case FDITRACKTYPE_250KBPS:
			case FDITRACKTYPE_500KBPS:
			case FDITRACKTYPE_281_25KBPS:
			case FDITRACKTYPE_312_5KBPS:
			case FDITRACKTYPE_343_75KBPS:
			case FDITRACKTYPE_375KBPS:
				break;
			default:
				return false;
		}
		byteNum += fdiHeader.tracks[index].pageNum * 0x100;
		if (fdiTracks)
			fdiTracks[index] = fdiHeader.tracks[index];
	}
	
	if (byteNum > fdiByteNum)
		return false;
	
	if (diskImageTrackNum)
		*diskImageTrackNum = fdiTrackNum;
	if (isWriteProtectionForced)
		*isWriteProtectionForced = isWriteProtected;
	
	return true;
}

bool FDIDiskImage::readDiskImageTrack(UINT32 diskImageTrackIndex,
									  FdiHeaderTrackType * fdiHeaderTrack,
									  bool isSetTrackBitNum) {
	if (fdiHeaderTrack->type == FDITRACKTYPE_BLANK)
		return true;
	
	UINT32 fdiTrackDataByteNum = fdiHeaderTrack->pageNum * 0x100;
	UINT8 fdiTrackData[fdiTrackDataByteNum];
	FdiTrackType * fdiTrack = (FdiTrackType *) fdiTrackData;
	
	UINT32 trackDataByteNum = fdiTrackDataByteNum - sizeof(FdiTrackType);
	UINT8 * trackData = fdiTrackData + sizeof(FdiTrackType);
	
	if (fread(&fdiTrackData, fdiTrackDataByteNum, 1, diskImageFile) != 1)
		return false;
	
	UINT32 fdiTrackBitNum = getBigEndianUINT32(&fdiTrack->bitNum);	
	if ((!fdiTrackBitNum) || (fdiTrackBitNum > (trackDataByteNum * 8)))
		return false;
	
	UINT32 headNum = getHeadNum();
	setTrackIndex(diskImageTrackIndex / headNum);
	setHeadIndex(diskImageTrackIndex % headNum);
	if (isSetTrackBitNum)
		setTrackBitNum(fdiTrackBitNum);
	else {
		UINT8 * data;
		UINT32 bitNum, bitIndex;
		getTrackData(&data, &bitNum, &bitIndex);
		
		UINT32 byteNum = (bitNum + 7) / 8;
		memcpy(data, trackData, byteNum);
	}
	
	return true;
}

bool FDIDiskImage::readDiskImage(UINT32 diskType,
								 bool * isWriteProtectionForced,
								 bool isSetTrackBitNum) {
	UINT32 diskImageTrackNum;
	FdiHeaderTrackType fdiHeaderTracks[DISK_3_5_TRACKNUM * DISK_3_5_HEADNUM];
	
	if (!readFdiHeader(diskType, diskImageFile,
					   &diskImageTrackNum, fdiHeaderTracks,
					   isWriteProtectionForced))
		return false;
	
	for (UINT32 index = 0; index < diskImageTrackNum; index++)
		if (!readDiskImageTrack(index,
								&fdiHeaderTracks[index],
								isSetTrackBitNum))
			return false;
	
	return true;
}

void FDIDiskImage::setFdiHeaderTrack(FloppyDiskImage * diskImage,
	struct FdiHeaderTrackType * fdiHeaderTrack) {
	UINT8 * data;
	UINT32 bitNum, bitIndex;
	diskImage->getTrackData(&data, &bitNum, &bitIndex);
	
	UINT32 byteNum = (bitNum + 7) / 8;
	UINT8 type = FDITRACKTYPE_BLANK;
	UINT8 pageNum = (sizeof(FdiTrackType) + (byteNum + 0xff)) / 0x100;
	
	if (diskImage->getNibbleNum()) {
		if (diskImage->getDiskType() == FLOPPYDISKTYPE_3_5)
			type = FDITRACKTYPE_500KBPS;
		else
			type = FDITRACKTYPE_250KBPS;
	}
	
	fdiHeaderTrack->type = type;
	fdiHeaderTrack->pageNum = (type == FDITRACKTYPE_BLANK) ? 0 : pageNum;
}

bool FDIDiskImage::writeFdiHeader(FILE * diskImageFile,
								  FloppyDiskImage * diskImage) {
	FdiHeaderType fdiHeader;
	
	UINT32 trackNum = diskImage->getTrackNum();
	UINT32 headNum = diskImage->getHeadNum();
	UINT32 fdiTrackNum = trackNum * headNum;
	
	memset(&fdiHeader, 0, sizeof(fdiHeader));
	
	memcpy(fdiHeader.signature, FDISIGNATURE, strlen(FDISIGNATURE));
	fdiHeader.signature[25] = 13;
	fdiHeader.signature[26] = 10;
	memset(fdiHeader.creator, 0x20, sizeof(fdiHeader.creator));
	memcpy(fdiHeader.creator, FDICREATOR, strlen(FDICREATOR));
	fdiHeader.crlf[0] = 13;
	fdiHeader.crlf[1] = 10;
	memset(fdiHeader.comment, 0x1a, sizeof(fdiHeader.comment));
	fdiHeader.eof = 0x1a;
	
	setBigEndianUINT16(&fdiHeader.version, FDIVERSION);
	setBigEndianUINT16(&fdiHeader.ltrack, trackNum - 1);
	fdiHeader.lhead = diskImage->getHeadNum() - 1;
	switch (diskImage->getDiskType()) {
		case FLOPPYDISKTYPE_5_25:
			fdiHeader.type = FDIDISKTYPE_5_25;
			fdiHeader.rotspeed = (UINT8) (DISK_5_25_MOTORRPM - 128);
			fdiHeader.flags = 0;
			fdiHeader.tpi = FDITPI_192;
			fdiHeader.headwidth = FDITPI_192;
			break;
		case FLOPPYDISKTYPE_3_5:
			fdiHeader.type = FDIDISKTYPE_3_5;
			fdiHeader.rotspeed = (UINT8) (DISK_3_5_MOTORRPM_1 - 128);
			fdiHeader.flags = 0;
			fdiHeader.tpi = FDITPI_96;
			fdiHeader.headwidth = FDITPI_96;
			break;
		default:
			return false;
	}
	
	UINT32 headerByteNum = FDIHEADER_BLOCKBYTENUM;
	if (fdiTrackNum > FDIHEADER_FIRSTBLOCKTRACKNUM)
		headerByteNum += FDIHEADER_BLOCKBYTENUM;
	
	for (UINT32 index = 0; index < fdiTrackNum; index++) {
		diskImage->setTrackIndex(index / headNum);
		diskImage->setHeadIndex(index % headNum);
		setFdiHeaderTrack(diskImage, &fdiHeader.tracks[index]);
	}
	
	bool isError = false;
	if (fwrite(&fdiHeader, headerByteNum, 1, diskImageFile) != 1)
		isError = true;
	
	return true;
}

bool FDIDiskImage::writeDiskImageTrack(UINT32 trackIndex,
									   UINT32 headIndex,
									   FILE * diskImageFile,
									   FloppyDiskImage * diskImage) {
	diskImage->setTrackIndex(trackIndex);
	diskImage->setHeadIndex(headIndex);
	
	if (!diskImage->getNibbleNum())
		return true;
	
	UINT8 * data;
	UINT32 bitNum, bitIndex;
	diskImage->getTrackData(&data, &bitNum, &bitIndex);
	
	UINT32 byteNum = (bitNum + 7) / 8;
	UINT32 pageNum = (sizeof(FdiTrackType) + (byteNum + 0xff)) / 0x100;
	if (pageNum >= 0x100)
		return false;
	
	UINT32 fdiTrackDataByteNum = pageNum * 0x100;
	UINT8 fdiTrackData[fdiTrackDataByteNum];
	FdiTrackType * fdiTrack = (FdiTrackType *) fdiTrackData;
	
	UINT8 * trackData = fdiTrackData + sizeof(FdiTrackType);
	
	memset(fdiTrackData, 0, fdiTrackDataByteNum);
	setBigEndianUINT32(&fdiTrack->bitNum, bitNum);
	setBigEndianUINT32(&fdiTrack->bitIndex, 0);
	memcpy(trackData, data, byteNum);
	
	if (fwrite(fdiTrackData, fdiTrackDataByteNum, 1, diskImageFile) != 1)
		return false;
	
	return true;
}

bool FDIDiskImage::writeDiskImage(FILE * diskImageFile,
								  FloppyDiskImage * diskImage) {
	if (!writeFdiHeader(diskImageFile, diskImage))
		return false;
	
	UINT32 trackNum = diskImage->getTrackNum();
	UINT32 headNum = diskImage->getHeadNum();
	for (UINT32 trackIndex = 0; trackIndex < trackNum; trackIndex++)
		for (UINT32 headIndex = 0; headIndex < headNum; headIndex++)
			if (!writeDiskImageTrack(trackIndex, headIndex,
									 diskImageFile, diskImage))
				return false;
	
	return true;
}

bool FDIDiskImage::writeDiskImage(FloppyDiskImage * diskImage) {
	char * diskImagePath;
	FILE * diskImageFile;
	
	asprintf(&diskImagePath, "%s%s", diskImage->getDiskImagePath(),
			 FDIPATHSUFFIX);
	
	if (!(diskImageFile = fopen(diskImagePath, "wb")))
		return false;
	
	bool isError = !FDIDiskImage::writeDiskImage(diskImageFile, diskImage);
	
	fclose(diskImageFile);
	
	if (diskImagePath)
		free(diskImagePath);
	
	return !isError;
}
